/****************************************************************************
 **
 ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
 ** All rights reserved.
 ** Contact: Nokia Corporation (qt-info@nokia.com)
 **
 ** This file is part of the examples of the Qt Toolkit.
 **
 ** $QT_BEGIN_LICENSE:LGPL$
 ** Commercial Usage
 ** Licensees holding valid Qt Commercial licenses may use this file in
 ** accordance with the Qt Commercial License Agreement provided with the
 ** Software or, alternatively, in accordance with the terms contained in
 ** a written agreement between you and Nokia.
 **
 ** GNU Lesser General Public License Usage
 ** Alternatively, this file may be used under the terms of the GNU Lesser
 ** General Public License version 2.1 as published by the Free Software
 ** Foundation and appearing in the file LICENSE.LGPL included in the
 ** packaging of this file.  Please review the following information to
 ** ensure the GNU Lesser General Public License version 2.1 requirements
 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
 **
 ** In addition, as a special exception, Nokia gives you certain additional
 ** rights.  These rights are described in the Nokia Qt LGPL Exception
 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
 **
 ** GNU General Public License Usage
 ** Alternatively, this file may be used under the terms of the GNU
 ** General Public License version 3.0 as published by the Free Software
 ** Foundation and appearing in the file LICENSE.GPL included in the
 ** packaging of this file.  Please review the following information to
 ** ensure the GNU General Public License version 3.0 requirements will be
 ** met: http://www.gnu.org/copyleft/gpl.html.
 **
 ** If you have questions regarding the use of this file, please contact
 ** Nokia at qt-info@nokia.com.
 ** $QT_END_LICENSE$
 **
 ****************************************************************************/

#include <QtGui>
#include <QTemporaryFile>
#include "codeeditor.h"
#include "errorlist.h"

CodeEditor::CodeEditor(QWidget *parent) : QPlainTextEdit(parent), c(0) {
  lineNoteArea = new LineNoteArea(this);
  lineNumberArea = new LineNumberArea(this);
  setTabStopWidth(fontMetrics().width("  "));
  connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(updateLeftAreaWidth(int)));
  connect(this, SIGNAL(updateRequest(QRect, int)), this, SLOT(updateLeftArea(QRect, int)));
  connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(highlightCurrentLine()));
  updateLeftAreaWidth(0);
  highlightCurrentLine();
}

void CodeEditor::setPath(QString path) {
  this->filepath = path;
}

QString CodeEditor::path() {
  return this->filepath;
}

int CodeEditor::lineNumberAreaWidth() {
  int digits = 1;
  int max = qMax(1, blockCount());

  while (max >= 10) {
    max /= 10;
    ++digits;
  }

  int space = 3 + fontMetrics().width(QLatin1Char('9')) * digits;
  return space;
}

void CodeEditor::updateLeftAreaWidth(int /* newBlockCount */) {
  setViewportMargins(lineNumberAreaWidth() + 18, 0, 0, 0);
}

void CodeEditor::updateLeftArea(const QRect &rect, int dy) {
  if (dy) {
    lineNumberArea->scroll(0, dy);
    lineNoteArea->scroll(0, dy);
  } else {
    lineNumberArea->update(0, rect.y(), lineNumberArea->width(), rect.height());
    lineNoteArea->update(lineNumberAreaWidth(), rect.y(), 18, rect.height());
  }

  if (rect.contains(viewport()->rect())) {
    updateLeftAreaWidth(0);
  }
}

void CodeEditor::resizeEvent(QResizeEvent *e) {
  QPlainTextEdit::resizeEvent(e);
  QRect cr = contentsRect();
  lineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height()));
  lineNoteArea->setGeometry(QRect(cr.left() + lineNumberAreaWidth(), cr.top(), 18, cr.height()));
}

void CodeEditor::highlightCurrentLine() {
  QList<QTextEdit::ExtraSelection> extraSelections;

  if (!isReadOnly()) {
    QTextEdit::ExtraSelection selection;
    QColor lineColor = QColor(Qt::yellow).lighter(160);
    selection.format.setBackground(lineColor);
    selection.format.setProperty(QTextFormat::FullWidthSelection, true);
    selection.cursor = textCursor();
    selection.cursor.clearSelection();
    extraSelections.append(selection);
  }

  setExtraSelections(extraSelections);
}

void CodeEditor::lineNumberAreaPaintEvent(QPaintEvent *event) {
  QPainter painter(lineNumberArea);
  painter.fillRect(event->rect(), Qt::lightGray);
  QTextBlock block = firstVisibleBlock();
  int blockNumber = block.blockNumber();
  int top = (int) blockBoundingGeometry(block).translated(contentOffset()).top();
  int bottom = top + (int) blockBoundingRect(block).height();

  while (block.isValid() && top <= event->rect().bottom()) {
    if (block.isVisible() && bottom >= event->rect().top()) {
      QString number = QString::number(blockNumber + 1);
      painter.setPen(Qt::black);
      painter.drawText(0, top, lineNumberArea->width(), fontMetrics().height(),
                       Qt::AlignRight, number);
    }

    block = block.next();
    top = bottom;
    bottom = top + (int) blockBoundingRect(block).height();
    ++blockNumber;
  }
}

void CodeEditor::lineNoteAreaPaintEvent(QPaintEvent *event)
{
  QPainter painter(lineNoteArea);
  painter.fillRect(event->rect(), Qt::gray);
}

//auto complete
void CodeEditor::setCompleter(QCompleter *completer) {
  if (c) {
    QObject::disconnect(c, 0, this, 0);
  }

  c = completer;

  if (!c) {
    return;
  }

  c->setWidget(this);
  c->setCompletionMode(QCompleter::PopupCompletion);
  c->setModelSorting(QCompleter::CaseSensitivelySortedModel);
  c->setCaseSensitivity(Qt::CaseSensitive);
  QObject::connect(c, SIGNAL(activated(QString)),
                   this, SLOT(insertCompletion(QString)));
}

QCompleter *CodeEditor::completer() const {
  return c;
}

void CodeEditor::insertCompletion(const QString& completion) {
  if (c->widget() != this) {
    return;
  }

  QTextCursor tc = textCursor();
  int extra = completion.length() - c->completionPrefix().length();
  tc.movePosition(QTextCursor::Left);
  tc.movePosition(QTextCursor::EndOfWord);
  tc.insertText(completion.right(extra));
  setTextCursor(tc);
}

QString CodeEditor::textUnderCursor() const {
  QTextCursor tc = textCursor();
  tc.select(QTextCursor::WordUnderCursor);
  return tc.selectedText();
}

void CodeEditor::focusInEvent(QFocusEvent *e) {
  if (c) {
    c->setWidget(this);
  }

  QPlainTextEdit::focusInEvent(e);
}

void CodeEditor::keyPressEvent(QKeyEvent *e) {
  if (e->matches(QKeySequence::Save)) {
    emit saveRequested();
    return;
  }

  if (c && c->popup()->isVisible()) {
      // The following keys are forwarded by the completer to the widget
     switch (e->key()) {
     case Qt::Key_Enter:
     case Qt::Key_Return:
     case Qt::Key_Escape:
     case Qt::Key_Tab:
     case Qt::Key_Backtab:
          e->ignore();
          return; // let the completer do default behavior
     default:
         break;
     }
  } else {
    //auto indent
    if(e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) {
      QTextCursor tc = textCursor();
      tc.select(QTextCursor::LineUnderCursor);
      QString upper = tc.selectedText();
      QChar tab(9);
      QString tabs;
      for(int i = 0; i < upper.length(); i++) {
        if(upper.at(i).unicode() == 9) tabs.append(tab);
        else break;
      }
      QPlainTextEdit::keyPressEvent(e);
      QString indent_text = tabs;
      if(upper.endsWith("{")) {
        indent_text.append(tab).append("\n").append(tabs).append("}");
      } else if(upper.endsWith("(")) {
        indent_text.append(tab).append("\n").append(tabs).append("}");
      }
      textCursor().insertText(indent_text);
      return;
    }
  }

  bool isShortcut = ((e->modifiers() & Qt::ControlModifier) && e->key() == Qt::Key_Space);
  if (!c || !isShortcut) // do not process the shortcut when we have a completer
      QPlainTextEdit::keyPressEvent(e);
  if(e->isAutoRepeat()) return;

  bool isDot = (e->text() == ".");
  if(e->key() != Qt::Key_Backspace && (isShortcut || isDot))
    updateCompleter();

  const bool ctrlOrShift = e->modifiers() & (Qt::ControlModifier | Qt::ShiftModifier);
  if (!c || (ctrlOrShift && e->text().isEmpty()))
      return;

  static QString eow("~!@#$%^&*()_+{}|:\"<>?,/;'[]\\-="); // end of word
  bool hasModifier = (e->modifiers() != Qt::NoModifier) && !ctrlOrShift;
  QString completionPrefix = textUnderCursor();

  bool isShowing = c->popup()->isVisible();
  if (!isShortcut && !isDot && (hasModifier || e->text().isEmpty()
                      || (!isShowing && completionPrefix.length() < 3)
                      || eow.contains(e->text().right(1)))) {
      c->popup()->hide();
      QCompleter* c2 = c;
      c = NULL;
      delete c2;
      return;
  }

  if (completionPrefix != c->completionPrefix()) {
      c->setCompletionPrefix(completionPrefix);
      c->popup()->setCurrentIndex(c->completionModel()->index(0, 0));
  }
  QRect cr = cursorRect();
  cr.setWidth(c->popup()->sizeHintForColumn(0)
              + c->popup()->verticalScrollBar()->sizeHint().width());
  c->complete(cr); // popup it up!
}

void CodeEditor::updateCompleter() {
  QTemporaryFile tmp;
  if (tmp.open()) {
    tmp.write(this->toPlainText().toUtf8());
    tmp.close();
  }

  QStringList args;
  args.append("-in=" + tmp.fileName());
  args.append("-f=emacs");
  args.append("autocomplete");
  args.append(this->path());
  QString pos = QString::number(this->textCursor().position());
  args.append(pos);

  QProcess process;
  QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
  #ifndef Q_OS_WIN32
    env.insert("GOPATH", env.value("GOPATH") + projectpath.prepend(";"));
  #else
    env.insert("GOPATH", env.value("GOPATH") + projectpath.prepend(":"));
  #endif
  process.setProcessEnvironment(env);
  process.start("gocode", args);
  if(!process.waitForFinished(500))
    return;

  QStringList lines = QString::fromUtf8(process.readAllStandardOutput())
      .split("\n", QString::SkipEmptyParts);

  QStringList names;
  QString line;
  foreach(line, lines) {
    names.append(line.split(",").at(0));
  }
  names.sort();
  QStringListModel* model = new QStringListModel(names);
  QCompleter* completer = new QCompleter(model);
  setCompleter(completer);
}

bool CodeEditor::format(ErrorList *errlist) {
  //copy the text block, create tmp file
  QTemporaryFile tmp;
  if (tmp.open()) {
    tmp.write(this->toPlainText().toUtf8());
    tmp.close();
  }

  QStringList args;
  args.append("-tabwidth=2");
  args.append(tmp.fileName());
  QProcess process;
  process.start("gofmt", args);
  if(!process.waitForFinished(500))
    return false;
  int code = process.exitCode();
  if(code == 0) {
    setPlainText(QString::fromUtf8(process.readAllStandardOutput()));
    return true;
  } else if(code == 2) {
    QStringList error_output = QString::fromUtf8(process.readAllStandardError())
        .split("\n", QString::SkipEmptyParts);
    QString error_block;
    foreach(error_block, error_output) {
      QStringList error_part = error_block.split(":");
      int error_line = (error_part[1]).toInt();

      QString error_mess = QStringList(error_part.mid(3)).join(":");
      errlist->AddError(filepath, error_mess, error_line - 1);
    }
  }
  return false;
}

void CodeEditor::setProjectPath(const QString &path) {
  projectpath = path;
}
